package trinidad;

import java.io.Serializable;

/**
 * Reprezentacja stanu gry
 */

public class GameState implements Serializable{
	public byte[][] state;
	
	/**
	 * move classification
	 */
	public static final int FAULT = 0;
	public static final int FINAL = 1;
	public static final int PARTIAL = 2;
	public static final int KILLING = 3;
	private final static int[] opponent={0,2,1};	
	public byte[] piCount;
		
	public GameState() {
		piCount = new byte[]{10,10};
		state = new byte[7][7];
		for (int a = 1; a < 6; a++) {
			int b = a % 2 + 1;
			int c = (a+1) % 2 + 1;
			setState(a, 0, Byte.parseByte(""+b));
			setState(a, 6, Byte.parseByte(""+b));
			setState(0, a, Byte.parseByte(""+c));
			setState(6, a, Byte.parseByte(""+c));
		}
	}
	
	public GameState(GameState initial) {
		state = new byte[7][7];
		for (int i = 0; i < 7; i++)
			for (int j = 0; j < 7; j++) state[i][j] = initial.getState(i, j);
		piCount = new byte[]{initial.piCount[0], initial.piCount[1]};
	}
	
	/**
	 * State GET method
	 * @param x
	 * @param y
	 * @return
	 */
	public byte getState(int x, int y) {
		return state[x][y];
	}
	
	public byte safeGetState(int x, int y) {
		if (x < 0 || x > 6 || y < 0 || y > 6) return (byte) -1;
		return state[x][y];		
	}
	
	/**
	 * State SET method
	 * @param x
	 * @param y
	 * @param val
	 */
	
	public void setState(int x, int y, byte val) {
		state[x][y] = val;
	}
	
	public GameState trans(Move mv) {
		GameState tmp = new GameState(this);
		tmp.setState(mv.ex, mv.ey, getState(mv.sx, mv.sy));
		tmp.setState(mv.sx, mv.sy, (byte)0);
		if (Math.abs(mv.sx - mv.ex) == 2) {
			int x = (mv.sx + mv.ex) / 2;
			int y = (mv.sy + mv.ey) / 2;
			tmp.piCount[tmp.getState(x, y) - 1]--;
			tmp.setState(x, y, (byte)0);
		}
		return tmp;
	}
	
	public int secureTrans(byte x1, byte y1, byte x2, byte y2, boolean normLock) {
		// check constraints
		if (x1 < 0 || x1 > 6 || x2 < 0 || x2 > 6) return FAULT;
		if (y1 < 0 || y1 > 6 || y2 < 0 || y2 > 6) return FAULT;

		// dest field must be empty
		if (getState(x2,y2) != 0) return FAULT;
		// src field must be occupied
		if (getState(x1,y1) == 0) return FAULT;
		
		// kill move
		boolean isKill = false;
		if (!((x1 == x2 && Math.abs(y1-y2) == 1) || (y1 == y2 && Math.abs(x1-x2) == 1))) {
			if (Math.abs(x1-x2) == 2 && Math.abs(y1-y2) == 2) {
				byte temp = getState((x1+x2)/2,(y1+y2)/2);
				if (temp == getState(x1,y1) || temp == 0) return FAULT; // kamikaze or nobody to kill
				// kill
				piCount[temp - 1]--;
				isKill = true;
				setState((x1+x2)/2, (y1+y2)/2, (byte) 0);
			} else return FAULT; // incorrect move detected
		} else if (normLock) return FAULT; // only kill allowed
		// move the player
		setState(x2, y2, getState(x1,y1));
		setState(x1, y1, (byte) 0);
		byte temp = getState(x2, y2);

		// check whether another killing move is possible
		if (isKill) {
			if (safeGetState(x2 - 1,y2 - 1) == opponent[temp] && safeGetState(x2 - 2, y2 - 2) == 0)
				return PARTIAL;
			if (safeGetState(x2 + 1,y2 - 1) == opponent[temp] && safeGetState(x2 + 2, y2 - 2) == 0)
				return PARTIAL;
			if (safeGetState(x2 - 1,y2 + 1) == opponent[temp] && safeGetState(x2 - 2, y2 + 2) == 0)
				return PARTIAL;
			if (safeGetState(x2 + 1,y2 + 1) == opponent[temp] && safeGetState(x2 + 2, y2 + 2) == 0)
				return PARTIAL;
		}
		// simple move
		return FINAL;
	}
	
	/**
	 * Generates all possible single-moves from this
	 * position. Warning: assumes that field x,y is
	 * occupied.
	 * @param x
	 * @param y
	 * @param isPartial
	 * @return
	 */
	public Move[] generatePossibleMoves(byte x, byte y, boolean isPartial) {
		ArrayListFX mList = new ArrayListFX();
		if (!isPartial) {
			if (safeGetState(x-1, y) == 0) mList.add(new Move(x, y, (byte)(x - 1), y));
			if (safeGetState(x+1, y) == 0) mList.add(new Move(x, y, (byte)(x + 1), y));
			if (safeGetState(x, y-1) == 0) mList.add(new Move(x, y, x, (byte)(y - 1)));
			if (safeGetState(x, y+1) == 0) mList.add(new Move(x, y, x, (byte)(y + 1)));
		}
		
		int opp = opponent[getState(x, y)];
		if (safeGetState(x-2, y-2) == 0)
			if (getState(x-1, y-1) == opp) mList.add(new Move(x, y, (byte)(x - 2),(byte)(y - 2)));
		if (safeGetState(x+2, y-2) == 0)
			if (getState(x+1, y-1) == opp) mList.add(new Move(x, y, (byte)(x + 2),(byte)(y - 2)));
		if (safeGetState(x-2, y+2) == 0)
			if (getState(x-1, y+1) == opp) mList.add(new Move(x, y, (byte)(x - 2),(byte)(y + 2)));
		if (safeGetState(x+2, y+2) == 0)
			if (getState(x+1, y+1) == opp) mList.add(new Move(x, y, (byte)(x + 2),(byte)(y + 2)));
		return mList.toMoveArray();
	}
	
	public Move[] generateBaseMoves(int player) {
		ArrayListFX mList = new ArrayListFX();
		for (byte x = 0; x < 7; x++)
			for (byte y = 0; y < 7; y++)
				if (getState(x, y) == player){
					if (safeGetState(x-1, y) == 0) mList.add(new Move(x, y, (byte)(x - 1), y));
					if (safeGetState(x+1, y) == 0) mList.add(new Move(x, y, (byte)(x + 1), y));
					if (safeGetState(x, y-1) == 0) mList.add(new Move(x, y, x, (byte)(y - 1)));
					if (safeGetState(x, y+1) == 0) mList.add(new Move(x, y, x, (byte)(y + 1)));
		}
		for (byte x = 0; x < 7; x++)
			for (byte y = 0; y < 7; y++)
				if (getState(x, y) == player){
					int opp = opponent[getState(x, y)];
					if (safeGetState(x-2, y-2) == 0)
						if (getState(x-1, y-1) == opp) mList.add(new Move(x, y, (byte)(x - 2),(byte)(y - 2)));
					if (safeGetState(x+2, y-2) == 0)
						if (getState(x+1, y-1) == opp) mList.add(new Move(x, y, (byte)(x + 2),(byte)(y - 2)));
					if (safeGetState(x-2, y+2) == 0)
						if (getState(x-1, y+1) == opp) mList.add(new Move(x, y, (byte)(x - 2),(byte)(y + 2)));
					if (safeGetState(x+2, y+2) == 0)
						if (getState(x+1, y+1) == opp) mList.add(new Move(x, y, (byte)(x + 2),(byte)(y + 2)));
				}
		return mList.toMoveArray();
	}
	
	public GameStateX[] getSolution(int player) {
		byte x, y;
		ArrayListFX gsx = new ArrayListFX();
		gsx.add(new GameStateX(this));
		Move[] mtr = this.generateBaseMoves(player);
		int startKill = -1;
		GameStateX tmp = (GameStateX) gsx.get(0);
		int i = 0;
		for (i = 0; i < mtr.length; i++) {
			gsx.add(tmp.makeState(mtr[i]));
			if (startKill == -1) 
				if (Math.abs(mtr[i].sx - mtr[i].ex) == 2) startKill = gsx.size() - 1;
		}
		if (startKill != -1) {
			i = startKill;		
			while (i < gsx.size()) {
				tmp = (GameStateX) gsx.get(i);
				x = tmp.getMadeBy().ex;
				y = tmp.getMadeBy().ey;
				mtr = tmp.getState().generatePossibleMoves(x, y, true);
				for (int j = 0; j < mtr.length; j++) {
					gsx.add(tmp.makeState(mtr[j]));
				}	
				i++;
			}
			//System.out.println("nxst: " + (i - 1));
		} //else System.out.println("nxst: " + i);
		return gsx.toGSXArray();
	}
	public String toString() {
		String stx = "";
		for (int a = 0; a < 7; a++) {
			char cx = 0;
			for (int b = 0; b < 7; b++) {
				cx += (char)state[a][b];
				cx *= 3;
			}
			stx = stx + cx;
		}
		return stx;
	}
	
	public String getString() {
		String stx = "";
		for (int a = 0; a < 7; a++) {
			for (int b = 0; b < 7; b++) {
				stx += state[b][a] + " ";
			}
			stx += "\n";
		}
		return stx;
	}
		
}
